package com.hccake.ballcat.codegen.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.hccake.ballcat.codegen.constant.FileGitLabTypeEnum;
import com.hccake.ballcat.codegen.constant.SqlTypeEnum;
import com.hccake.ballcat.codegen.constant.TemplateEntryTypeEnum;
import com.hccake.ballcat.codegen.datasource.WrappedDataSource;
import com.hccake.ballcat.codegen.engine.TemplateEngineDelegator;
import com.hccake.ballcat.codegen.engine.TemplateEngineTypeEnum;
import com.hccake.ballcat.codegen.exception.TemplateRenderException;
import com.hccake.ballcat.codegen.helper.GenerateHelper;
import com.hccake.ballcat.codegen.model.bo.FileEntry;
import com.hccake.ballcat.codegen.model.bo.TableDetails;
import com.hccake.ballcat.codegen.model.bo.TemplateFile;
import com.hccake.ballcat.codegen.model.dto.ExecSqlDTO;
import com.hccake.ballcat.codegen.model.dto.GeneratorOptionDTO;
import com.hccake.ballcat.codegen.model.dto.GitFile;
import com.hccake.ballcat.codegen.model.entity.TemplateEntry;
import com.hccake.ballcat.codegen.model.vo.ExecSqlVO;
import com.hccake.ballcat.codegen.service.GeneratorService;
import com.hccake.ballcat.codegen.service.TableInfoQuery;
import com.hccake.ballcat.codegen.service.TemplateEntryService;
import com.hccake.ballcat.codegen.util.DbTypeUtils;
import com.hccake.ballcat.codegen.util.GenerateUtils;
import com.hccake.ballcat.codegen.util.GitLabUtil;
import com.hccake.ballcat.common.core.exception.BusinessException;
import com.hccake.ballcat.common.model.result.SystemResultCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author Hccake
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {

	private final TableInfoQuery tableInfoQuery;

	private final TemplateEntryService templateEntryService;

	private final TemplateEngineDelegator templateEngineDelegator;

	private final GenerateHelper generateHelper;

	@Value("${gitlab.privateToken}")
	private String privateToken;

	@Value("${gitlab.hostIp}")
	private String hostIp;

	/**
	 * 动态路由数据连接
	 */
	@Resource(type = DataSource.class)
	private DynamicRoutingDataSource dynamicRoutingDataSource;

	/**
	 * 生成代码
	 * @param generatorOptionDTO 代码生成的一些配置信息
	 * @return 已生成的代码数据
	 */
	@Override
	public byte[] generatorCode(GeneratorOptionDTO generatorOptionDTO) throws IOException {
		// 获取生成后的文件项 map
		Map<String, FileEntry> map = getStringFileEntryMap(generatorOptionDTO);

		try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
				ZipOutputStream zip = new ZipOutputStream(outputStream)) {
			// 循环写入数据
			for (Map.Entry<String, FileEntry> entry : map.entrySet()) {
				FileEntry fileEntry = entry.getValue();
				// 只处理文件
				if (!TemplateEntryTypeEnum.FOLDER.equals(fileEntry.getType())) {
					// 添加到zip
					String filePath = entry.getKey();
					zip.putNextEntry(new ZipEntry(filePath));
					zip.write(fileEntry.getFileContent());
					zip.closeEntry();
				}
			}
			// 手动结束 zip，防止文件末端未被写入
			zip.finish();
			return outputStream.toByteArray();
		}
	}

	@Override
	public List<FileEntry> previewCode(GeneratorOptionDTO generateOptionDTO) {
		// 获取生成后的文件项 map
		Map<String, FileEntry> map = getStringFileEntryMap(generateOptionDTO);
		// 忽略大小写的排序
		return CollUtil.sort(map.values(), Comparator.comparing(FileEntry::getFilename, String.CASE_INSENSITIVE_ORDER));
	}

	/**
	 * 获得生成后的 代码地址：代码文件 的 map
	 * @param generateOptionDTO 生成参数
	 * @return Map<String, FileEntry>
	 */
	private Map<String, FileEntry> getStringFileEntryMap(GeneratorOptionDTO generateOptionDTO) {
		// 获取模板文件信息
		List<TemplateEntry> templateEntryList = templateEntryService.listByIds(generateOptionDTO.getTemplateEntryIds());
		List<TemplateFile> templateFiles = templateEntryService.convertToTemplateFile(templateEntryList);

		return getStringFileEntryMap(generateOptionDTO, templateFiles);
	}

	/**
	 * 获得生成后的 代码地址：代码文件 的 map
	 * @param generateOptionDTO 生成参数
	 * @param templateFiles 模板文件
	 * @return Map<String, FileEntry>
	 */
	private Map<String, FileEntry> getStringFileEntryMap(GeneratorOptionDTO generateOptionDTO,
			List<TemplateFile> templateFiles) {

		String[] tableNames = generateOptionDTO.getTableNames();
		Map<String, String> genProperties = generateOptionDTO.getGenProperties();
		String templateGroupKey = generateOptionDTO.getTemplateGroupKey();

		// 没有表数据则直接进行代码生成
		if (tableNames == null || tableNames.length == 0) {
			return generatorCode(genProperties, templateGroupKey, templateFiles);
		}

		// 有表数据，则根据表数据进行循环生成
		Map<String, FileEntry> map = new HashMap<>(templateFiles.size());
		for (String tableName : tableNames) {
			// 查询表详情
			TableDetails tableDetails = tableInfoQuery.queryTableDetails(tableName);
			// 生成代码
			Map<String, FileEntry> fileEntryMap = generatorCode(tableDetails, generateOptionDTO.getTablePrefix(),
					genProperties, templateGroupKey, templateFiles);
			map.putAll(fileEntryMap);
		}
		return map;
	}

	private Map<String, FileEntry> generatorCode(Map<String, String> genProperties, String templateGroupKey,
			List<TemplateFile> templateFiles) {
		return generatorCode(null, null, genProperties, templateGroupKey, templateFiles);
	}

	/**
	 * 代码生成
	 * @return Map<String, FileEntry>
	 */
	public Map<String, FileEntry> generatorCode(TableDetails tableDetails, String tablePrefix,
			Map<String, String> customProperties, String groupKey, List<TemplateFile> templateFiles) {

		Map<String, FileEntry> map = new HashMap<>(templateFiles.size());

		// 模板渲染
		Map<String, Object> context = generateHelper.getContext(tableDetails, tablePrefix, groupKey, customProperties);

		for (TemplateFile templateFile : templateFiles) {
			FileEntry fileEntry = new FileEntry();
			fileEntry.setId(templateFile.getId());
			fileEntry.setType(templateFile.getType());

			// 替换路径中的占位符
			String templateFilename = templateFile.getFilename();
			String filename = StrUtil.format(templateFilename, context);
			fileEntry.setFilename(filename);

			String parentFilePath = GenerateUtils.evaluateRealPath(templateFile.getParentFilePath(), context);
			fileEntry.setParentFilePath(parentFilePath);

			switch (fileEntry.getType()) {
				case TEMPLATE_FILE:
					String filePath = GenerateUtils.concatFilePath(parentFilePath, filename);
					fileEntry.setFilePath(filePath);
					// 文件内容渲染
					TemplateEngineTypeEnum engineTypeEnum = TemplateEngineTypeEnum.of(templateFile.getEngineType());

					try {
						String templateContent = StrUtil.str(templateFile.getFileContent(), StandardCharsets.UTF_8);
						String content = templateEngineDelegator.render(engineTypeEnum, templateContent, context);
						fileEntry.setFileContent(content.getBytes(StandardCharsets.UTF_8));
					}
					catch (TemplateRenderException ex) {
						String errorMessage = CharSequenceUtil.format("模板渲染异常，模板文件名：【{}】，错误详情：{}", templateFilename,
								ex.getMessage());
						throw new BusinessException(SystemResultCode.SERVER_ERROR.getCode(), errorMessage);
					}
					break;
				case BINARY_FILE:
					String binaryFilePath = GenerateUtils.concatFilePath(parentFilePath, filename);
					fileEntry.setFilePath(binaryFilePath);
					fileEntry.setFileContent(templateFile.getFileContent());
					break;
				case FOLDER:
					String currentPath = GenerateUtils.evaluateRealPath(templateFilename, context);
					fileEntry.setFilePath(GenerateUtils.concatFilePath(parentFilePath, currentPath));
					break;
				default:
					log.warn("错误的文件类型: {}", templateFile);
			}

			map.put(fileEntry.getFilePath(), fileEntry);
		}
		return map;
	}

	/***
	 * @description: 参数校验 gitPathList 路径是 包含 /blob/master folder： 文件加 路径包含： /tree/master
	 * 如果如果项目中中的 项目名 或 路径 包含 blob 或 tree ，可能存在问题
	 * @author ccd
	 * @date 2024/10/15 18:03
	 * @version 1.0
	 */
	private String argCheck(ExecSqlDTO execSqlDTO) {
		StringBuffer errMsg = new StringBuffer();
		// 可能传 空字符串， 坐下处理
		if (StringUtils.isNotEmpty(execSqlDTO.getFolder()) && execSqlDTO.getFolder().length() > 5) {
			if (execSqlDTO.getFolder().indexOf(FileGitLabTypeEnum.FOLDER_PATH.getMatchVal()) == -1) {
				errMsg.append("文件夹路径")
					.append(execSqlDTO.getFolder())
					.append("格式有问题，没有执行包含路径" + FileGitLabTypeEnum.FOLDER_PATH.getMatchVal());
			}
		}

		if (CollectionUtils.isNotEmpty(execSqlDTO.getGitPathList())) {
			for (String filePath : execSqlDTO.getGitPathList()) {
				if (StringUtils.isNotEmpty(filePath) && filePath.length() > 5
						&& filePath.indexOf(FileGitLabTypeEnum.FILE_PATH.getMatchVal()) == -1) {
					errMsg.append("文件路径")
						.append(filePath)
						.append("格式有问题，没有执行包含路径" + FileGitLabTypeEnum.FILE_PATH.getMatchVal());
				}
			}
		}
		return errMsg.toString();

	}

	public ExecSqlVO execSql(ExecSqlDTO execSqlDTO) {
		log.warn("执行execSql入参: {}", JSON.toJSONString(execSqlDTO));

		String errArgMes = argCheck(execSqlDTO);
		if (StringUtils.isNotEmpty(errArgMes)) {
			return new ExecSqlVO(errArgMes);
		}

		ExecSqlVO execSqlVO = new ExecSqlVO();

		WrappedDataSource dataSourceWrap = null;
		try {
			dataSourceWrap = (WrappedDataSource) dynamicRoutingDataSource.getDataSource(execSqlDTO.getDsName());
		}
		catch (Exception e) {
			log.error("根据" + execSqlDTO.getDsName() + "获取数据库失败", e);
			return new ExecSqlVO("根据" + execSqlDTO.getDsName() + "获取数据库失败");
		}

		if (null == dataSourceWrap) {
			StringBuffer logRs = new StringBuffer();
			logRs.append("通过" + execSqlDTO.getDsName() + "获取不到对应数据库");
			return new ExecSqlVO(logRs.toString());
		}

		DataSource dataSource = dataSourceWrap.getDataSource();
		List<GitFile> sqlFilePathList = getGitFile(execSqlDTO);

		if (CollectionUtils.isNotEmpty(sqlFilePathList)) {
			// 执行的sql
			execSqlVO.setGitFileSort(sqlFilePathList.stream().map(String::valueOf).collect(Collectors.joining(",")));
			DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
			LocalDateTime startTime = LocalDateTime.now();
			String startTimeFmt = startTime.format(formatter);
			log.info("开始执行sql文件, 开始时间为: " + startTimeFmt + " 需要处理的文件列表: " + JSONObject.toJSONString(sqlFilePathList));
			execSqlVO.setResult("执行成功");
			for (GitFile gitSqlPath : sqlFilePathList) {
				try {
					execSqlVO = execSql(execSqlDTO, dataSource, gitSqlPath);
				}
				catch (Exception e) {
					e.printStackTrace();
					execSqlVO.setResult("执行失败");
					execSqlVO.setErrFile(gitSqlPath.getGitFilePath());
					execSqlVO.setLogRs(e.getMessage());
					break;
				}
			}
			LocalDateTime endTime = LocalDateTime.now();
			String endTimeFmt = endTime.format(formatter);
			execSqlVO.setStartTime(startTimeFmt);
			execSqlVO.setEndTime(endTimeFmt);
			execSqlVO.setProcTime(String.valueOf(ChronoUnit.MILLIS.between(startTime, endTime)));
			log.info("执行sql文件结束, 结束时间为: " + endTimeFmt + " 处理结果: " + execSqlVO.getResult() + " 执行耗时: "
					+ execSqlVO.getProcTime());
		}
		else {
			return new ExecSqlVO("没有找到可执行的sql文件");
		}
		return execSqlVO;
	}

	/***
	 * @description: 执行sql
	 * @author ccd
	 * @date 2024/10/9 14:50
	 * @version 1.0
	 */
	private ExecSqlVO execSql(ExecSqlDTO execSqlDTO, DataSource dataSource, GitFile gitFile) throws Exception {
		String sql = getSql(execSqlDTO, gitFile);

		if (StringUtils.isEmpty(sql)) {
			StringBuffer logRs = new StringBuffer();
			logRs.append("通过" + gitFile.getGitFilePath() + ",分支" + gitFile.getBranch() + "获取不到文件内容信息");
			return new ExecSqlVO(logRs.toString());
		}

		ExecSqlVO tem = new ExecSqlVO();
		Exception error = null;
		Connection conn = null;
		try {
			conn = dataSource.getConnection();
			// 设置不自动提交
			conn.setAutoCommit(false);

			ScriptRunner runner = new ScriptRunner(conn);
			// 设置不自动提交
			runner.setAutoCommit(false);
			/*
			 * setStopOnError参数作用：遇见错误是否停止；
			 * （1）false，遇见错误不会停止，会继续执行，会打印异常信息，并不会抛出异常，当前方法无法捕捉异常无法进行回滚操作，无法保证在一个事务内执行；
			 * （2）true，遇见错误会停止执行，打印并抛出异常，捕捉异常，并进行回滚，保证在一个事务内执行；
			 */
			runner.setStopOnError(true);

			/*
			 * 按照那种方式执行 方式一：true则获取整个脚本并执行； 方式二：false则按照自定义的分隔符每行执行；
			 */
			runner.setSendFullScript(false);

			// 定义命令间的分隔符
			runner.setDelimiter(";");
			runner.setFullLineDelimiter(false);

			// 设置是否输出日志，null不输出日志，不设置自动将日志输出到控制台
			runner.setLogWriter(null);

			// 如果又多个sql文件，可以写多个runner.runScript(xxx),
			runner.runScript(new InputStreamReader(new ByteArrayInputStream(sql.getBytes(StandardCharsets.UTF_8))));

			conn.commit();
		}
		catch (Exception e) {
			conn.rollback();
			error = e;
		}
		finally {
			DbTypeUtils.close(conn);
		}
		if (error != null) {
			throw error;
		}
		return tem;
	}

	private String getSql(ExecSqlDTO execSqlDTO, GitFile gitFile) throws Exception {
		String sql = GitLabUtil.getFileContentFromRepository(hostIp, gitFile.getGitFilePath(), gitFile.getBranch(),
				privateToken);
		return sql;
	}

	private GitFile getGitFile(String filePath) {
		String branchStr = filePath.substring(filePath.indexOf(FileGitLabTypeEnum.FILE_PATH.getMatchVal())
				+ FileGitLabTypeEnum.FILE_PATH.getMatchVal().length());
		String branch = branchStr.substring(0, branchStr.indexOf("/"));
		return new GitFile(filePath.replaceFirst(FileGitLabTypeEnum.FILE_PATH.getMatchVal() + branch, ""), branch);
	}

	private GitFile getGitFolder(String filePath) {
		String branchStr = filePath.substring(filePath.indexOf(FileGitLabTypeEnum.FOLDER_PATH.getMatchVal())
				+ FileGitLabTypeEnum.FOLDER_PATH.getMatchVal().length());
		String branch = branchStr.substring(0, branchStr.indexOf("/"));
		return new GitFile(filePath.replaceFirst(FileGitLabTypeEnum.FOLDER_PATH.getMatchVal() + branch, ""), branch);
	}

	/***
	 * http://119.12.173.113:8186//channel-gateway-ke/blob/master/channel-gateway-ke-api/src/main/java/com/murong/ecp/mbp/cgw/api/model/CgwAbstractBaseReqBO.java
	 * http://119.12.173.113:8186/allprojects/bankProjects/KCB-Mobile-Banking/channel-gateway-group/channel-gateway-ke/tree/master/channel-gateway-ke-api
	 */
	private List<GitFile> getGitFile(ExecSqlDTO execSqlDTO) {
		List<GitFile> rsFileList = new ArrayList<>();
		// ddl 文件list
		List<GitFile> ddlList = new ArrayList<>();
		List<GitFile> dmlList = new ArrayList<>();
		if (CollectionUtils.isNotEmpty(execSqlDTO.getGitPathList())) {
			for (String filePath : execSqlDTO.getGitPathList()) {
				if (filePath.endsWith(".sql") && filePath.length() > 5) {
					GitFile gitFile = getGitFile(filePath);
					String fileName = getFileNameByPath(gitFile.getGitFilePath());

					if (fileName.toUpperCase(Locale.ROOT).contains(SqlTypeEnum.DDL.getMatchVal())) {
						ddlList.add(gitFile);
					}
					else {
						// 不是ddl， 默认都是dml
						dmlList.add(gitFile);
					}
				}
			}
		}

		// git文件夹内容 ， 读取文件夹下面所有文件内容
		if (StringUtils.isNotEmpty(execSqlDTO.getFolder()) && execSqlDTO.getFolder().length() > 5) {
			GitFile gitFile = getGitFolder(execSqlDTO.getFolder());

			List<String> foldFileList = GitLabUtil.getFilePathByFolderRepository(hostIp, gitFile.getGitFilePath(),
					gitFile.getBranch(), privateToken);
			if (CollectionUtils.isNotEmpty(foldFileList)) {
				for (String filePath : foldFileList) {
					if (filePath.endsWith(".sql")) {
						String fileName = getFileNameByPath(filePath);
						if (fileName.toUpperCase(Locale.ROOT).contains(SqlTypeEnum.DDL.getMatchVal())) {
							ddlList.add(new GitFile(filePath, gitFile.getBranch()));
						}
						else {
							// 不是ddl， 默认都是dml
							dmlList.add(new GitFile(filePath, gitFile.getBranch()));
						}
					}
				}
			}
		}

		log.info("ddlList---{}", JSON.toJSONString(ddlList));
		log.info("dmllist---{}", JSON.toJSONString(dmlList));

		if (CollectionUtils.isNotEmpty(ddlList)) {
			rsFileList.addAll(ddlList);
		}
		if (CollectionUtils.isNotEmpty(dmlList)) {
			rsFileList.addAll(dmlList);
		}

		return rsFileList;
	}

	/***
	 * @description: 根据文件路径 获取文件名
	 * @author ccd
	 * @date 2024/10/11 16:59
	 * @version 1.0
	 */
	private String getFileNameByPath(String filePath) {
		String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
		return fileName;
	}

}
